//Steve Hipolito, Daniel Beraun - Project 1

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

/**
 * A class that parses the page content.
 *
 */
public class PageParser {
	private PageRetriever pr;
	private PageAnalyzer pa;
	
	/** Queue of Parseables to be parsed. */
	private Queue<Parseable> parseables = new LinkedList<Parseable>();

	/** ArrayList of links (as strings) to keep track of visited links */
	private ArrayList<String> visitedLinks = new ArrayList<String>();

	public PageParser(final PageRetriever the_pr, final PageAnalyzer the_pa) {
		pr = the_pr;
		pa = the_pa;
	}
	
	public void addStringAndURLToQueueToParse(String string, String url, String main) {
		parseables.add(new Parseable(string, url, main));
	}

	/**
	 * Parses through a string (source code of a web page) and finds/adds words to
	 * the words array list. Also finds/adds unvisited links to the links queue.
	 * PageAnalyzer updates for each page crawled, each word found/added, and
	 * each embedded URL found/added.
	 * 
	 * @param string The source code of the web page.
	 * @param url The URL as a string.
	 * @throws IOException
	 * @author Steve Hipolito
	 */
	public void parse() throws IOException {
		// after calling this method, a new web page had been crawled
		pa.crawledPages++;
		
		Parseable p = parseables.remove();
		String string = p.string();
		String url = p.url();
		String main = p.main();
		
		boolean inTag = false; // <..(char)..>
		boolean inScript = false; // <..(char)..script>(skip these)</script>

		String currentWord = "";
		// parses through the contents string (by char)
		// has 3 cases: if the current char is in a tag, or if part of
			// a script, or if not in a tag and not part of a script
		for (int i = 0; i < string.length(); i++) {
			// case 1 - current parsed char is in a tag
			if (inTag) {
				if (string.charAt(i) == '>') {
					inTag = false;
				}
				// else if current parsed char is in <script>, 
					// <title>, or <style>
				else if (string.substring(i - 1).startsWith("<script")
						|| string.substring(i - 1).startsWith("<style")) {
					inScript = true;
					inTag = false;
				}
				// else if the current tag the current parsed char is 
					// in is a link tag, get the link
				else if (string.substring(i).startsWith("a href")) {
					pa.foundLinks++;
					
					// skip to the link part
					while (string.charAt(i) != '"')
						i++; // i is now on the opening " char
					i++; // link string starts at i char now

					int j = i;
					// look for the closing " to get link string end
					while (string.charAt(j) != '"')
						j++; // j char is now the closing "

					String preLink = "";
					// if link is not absolute url
					if (!string.substring(i).startsWith("http")) {
						if (string.substring(i).startsWith("/")) {
							preLink = main;
						} else {
							int trim = url.lastIndexOf('/');
							// trim the end of the link to the last /
							preLink = url.substring(0, trim + 1);
						}
					}

					String link = preLink + string.subSequence(i, j);

					// if link has not been visited yet, 
						// and it is a valid (for this project) link
					if (!visitedLinks.contains(link) && 
							(link.endsWith("html") || 
							link.endsWith("txt") || 
							link.endsWith("/"))) {
						pr.addURLStringToQueueToScan(link);
						visitedLinks.add(link);
						pa.uniqueLinks++;
					}

					i = j; // move on
				}
			}
			// case 2 - current parsed char is in a script
			else if (inScript) {
				// we know the script ends when the next '</' shows up
				if (string.substring(i).startsWith("</")) {
					inTag = true;
					inScript = false;
				}
			}
			// case 3 - current parsed char is out of a tag/script
			else {
				// if current parsed char is an opening tag
				if (string.charAt(i) == '<') {
					inTag = true;
					// if there was a word being built, add it as it is now
					if (!currentWord.equalsIgnoreCase("")) {
						pa.addWord(currentWord);
						currentWord = "";
					}
				}
				// else if current parsed char is NOT valid
				else if (!isCharValid(string.charAt(i)) &&
						!(string.charAt(i) == '\'' && 
							isCharValid(string.charAt(i-1))) &&
						!(string.charAt(i) == '-' && 
							isCharValid(string.charAt(i-1)) && 
							isCharValid(string.charAt(i+1)))) {
					// if there was a word being built, add it as it is now
					if (!currentWord.equalsIgnoreCase("")) {
						pa.addWord(currentWord);
						currentWord = "";
					}
					// don't count formatting/special strings 
						// (&nbsp; or &amp; etc...)
					if (string.charAt(i) == '&' && 
							isCharValid(string.charAt(i + 1))) {
						while (string.charAt(i) != ';' && string.charAt(i) != ' ')
							i++;
					}
				}
				// else current parsed char is valid
				else {
					// continue building the current word
					currentWord = currentWord + string.charAt(i);
				}
			} // end 3 cases
		} // end for

		// while there are still links needed to be scanned
		while (!pr.isLinksQueueEmpty()) {
			pr.scanPage();
		}

		// stop timer
		if (pa.timerStop == 0) {
			pa.timerStop = System.currentTimeMillis();
		}
	} // end parse
	
	private boolean isCharValid(char c) {
		return ((c >= 65 && c <= 90) || (c >= 97 && c <= 122) || Character.isDigit(c));
	}
}
